#include "TokenStream.h"
#include "Number.hpp"
#include "Identifier.h"
#include "CharInfo.h"
#include "Exception.hpp"

namespace cal
{

TokenStream::TokenStream(std::istream& is)
	: _is{ is }, _eof{ is.eof() }, _buffer{ }, _line{ 0 }, _col{ 0 }
{}

Token TokenStream::get() 
{
	if (!_buffer.empty()) return _buffer.get();
	Token token;
	if (_eof) return token;
	while (true) {
		char c = _is.peek();
		if (_is.eof()) {
			_eof = true;
			return Token{};
		} 
		if (is_ignorable_white_space(c)) {
			_is.get();
			++_col;		// ! \t 目前看作占一位
			continue;
		}
		token.line  = _line;
		token.begin = _col;
		
		if (is_identifier_start(c)) {
			Identifier id;
			_is >> id;
			_col += id.size();
			auto kind = get_keyword_tokenkind(id.str());
			if (kind == tok::unknown) kind = tok::identifier;
			token.value = id.str();
			token.kind  = kind;
			token.end   = _col;
			return token;
		}

		switch (c) {
			case '0': case '1': case '2': case '3': case '4': case '5': 
			case '6': case '7': case '8': case '9': case '.':
			{
				header::Number num;
				_is >> num;
				if (_is.fail()) {
					_is.clear();
					// ! 在此前 is.peek() = '.', 由于 throw, 会被 Parse 捕获而调用 clear_mess(), 
					// ! clear_mess() 中又会调用 TokenStream::get() 形成循环调用, 造成 bug
					_is.get();	
					throw Exception{"illegal number: '.'"};
				}
				_col += num.width();
				token.kind  = tok::number;
				token.value = num.str();
				token.end   = _col;
				return token;
			}
			/* operator */
#define OPERATOR(X, Y) case Y:
#include "TokenKinds.def"
			{
				token.kind = get_opeartor_tokenkind(_is.get());
				++_col;
				token.end  = _col;
				if (token.kind == tok::eocl) {
					++_line;
					_col = 0;
				}
				return token;
			}
			default: 
			{
				// ! 在此前 is.peek() 为 illegal terminal character , 由于 throw, 会被 Parse 捕获而调用 clear_mess(), 
				// ! clear_mess() 中又会调用 TokenStream::get() 形成循环调用, 造成 bug
				_is.get();
				throw Exception{ "illegal terminal character: '" + std::string{c} + "'" };
			}
		}
	}
	return token;
}

Token TokenStream::peek() 
{
	if (_buffer.empty()) {
		_buffer.push_peek(get());
	}
	return _buffer.peek();
}

void TokenStream::putback(const Token& token)
{
	_buffer.putback(token);
}

}   // namespace cal
